#!/usr/bin/env python
import unittest         ## this script is about tests

from pytitan import *   ## module to test and...
from sys import *       ## ...'sys' are the only modules I usually import with '*'

# util imports
import difflib

####################

def diff(utest, result, expected):
    if result != expected:
        diff = difflib.ndiff( map(str, result), map(str, expected) )
        msg = "\n".join(diff)
        utest.fail("Result and expected differ ('-' too much, '+' missing):\n%s" % msg)

#####################

class TestPlainMoves(unittest.TestCase):
    def setUp(self):
        setTerrain(tplains)
    def testSymmerty(self):
        """all moves on Plains are symmetrical"""
        hexes = range(0, 27) # [0, 1, ..., 26], all hexes that are part of the field
        t = getTerrain()
        for h1 in hexes:
            for h2 in hexes:
                a, b = t.couldStep(h1, h2), t.couldStep(h2, h1)
                self.assertEquals( a,b,  "%s-to-%s:%s, %s-to-%s:%s" % (h1,h2,a, h2,h1,b) )
    def testNotSelf(self):
        """must not allow move to self hex"""
        hexes = range(0, 27)
        t = getTerrain()
        for hex in hexes:
            self.assertFalse(t.couldStep(hex, hex))

class TestTerrainPlains(unittest.TestCase):
    def testOnfield(self):
        t = tplains
        ins = range(27)                   # [0..26]
        for hex in ins:
            self.assertTrue(t.isOnfield(hex), "%s" % hex)

    def testOutfield(self):
        t = tplains
        outs = [27, 28, 29, 30, 31, 32]   # 6
        for hex in outs:
            self.assertFalse(t.isOnfield(hex), "%s" % hex)

    def testIsMoveoutOutside(self):
        t = tplains
        hexes = range(33)
        for hex in hexes:
            self.assertEquals(not t.isOnfield(hex), t.isHexMoveoutIfPossible(hex), hex)

class TestGetMoveHexes(unittest.TestCase):
    def setUp(self):
        setTerrain(tplains)
    def testEnter1(self):
        """test creature with skill 1 entering battle"""
        self.assertFalse(getTerrain().isOnfield(27))
        a = CXlot1('Xbb', 27)
        ms = sorted( a.getMoveHexes([]) )
        self.assertEquals(ms,  [0, 3, 7, 12])
    def testEnter1Blocked(self):
        """test creature with skill 1 entering battle"""
        self.assertFalse(getTerrain().isOnfield(27))
        a = CXlot1('Xbb', 27)
        ms = sorted( a.getMoveHexes([0, 7]) )
        self.assertEquals(ms,  [3, 12])
    def testEnter2(self):
        """test creature with skill 2 entering battle"""
        self.assertFalse(getTerrain().isOnfield(28))
        a = COgre('Otto', 28)
        ms = sorted( a.getMoveHexes([]) )
        self.assertEquals(ms,  [0, 1, 2, 3, 4, 5, 6])
    def testEnter2Blocked(self):
        """test creature with skill 2 entering battle"""
        a = COgre('Otto', 28)
        ms = sorted( a.getMoveHexes([0]) )
        self.assertEquals(ms,  [ 1, 2, 4, 5, 6])
        ms = sorted( a.getMoveHexes([2]) )
        self.assertEquals(ms,  [ 0, 1, 3, 4, 5])
        ms = sorted( a.getMoveHexes([1]) )
        self.assertEquals(ms,  [ 0, 2, 3, 4, 5, 6])
    def testEnter2Outblocked(self):
        """test creature with skill 2 can not enter battle"""
        self.assertFalse(getTerrain().isOnfield(28))
        a = COgre('Otto', 28)
        ms = sorted( a.getMoveHexes([0, 1, 2]) )
        self.assertEquals(ms,  [28])  # stays instread of empty


class TestKindToKid(unittest.TestCase):
    def testBijjective(self):
        """test for double values in KIND_TO_KID"""
        v = KIND_TO_KID.values()
        self.assertEquals(len(v), len(set(v)))


OFFHEX = 27        ## a test hex outside the battlefield
COLOR = "Bk"       ## the color of the moving army
COLOR2 = "Rd"      ## the color of the other army

class TestMovesGenPlains(unittest.TestCase):
    """Test MovesGen class that generates possible move-combinations
    from a given situation"""

    def setUp(self):
        setTerrain(tplains)

    def testEnterBattlefieldSkill1(self):
        """Test MovesGen for a creature with skill 1 entering"""
        x = CXlot1('Xaa', OFFHEX) # CXlot1: skill=1
        army = Army(COLOR, [ x ])
        army2 = Army(COLOR2, [ ])
        mg = MovesGen(army, army2)
        # run
        moves = sorted( mg.moves() ) # sorted() forces iterator to list
        # expected
        expected = sorted([ [(x,0)], [(x,3)], [(x,7)], [(x,12)] ])
        diff(self, moves, expected)

    def testEnterBattlefieldSkill2(self):
        """Test MovesGen for a creature with skill 2 entering"""
        mg = MovesGen()
        x = COgre('Otto', OFFHEX) # COgre: skill=2
        army = Army(COLOR, [ x ])
        army2 = Army(COLOR2, [ ])
        # run
        moves = sorted( mg.moves(army, army2) ) # sorted() forces iterator to list
        # expected
        expected = sorted([
                # 1 step:
                [(x,0)], [(x,3)], [(x,7)], [(x,12)],
                # 2 steps:
                [(x,1)], [(x,4)], [(x,8)], [(x,13)], [(x,18)],
                ])
        diff(self, moves, expected)

    def testEnterBattlefieldSkill3(self):
        """Test MovesGen for a creature with skill 3 entering"""
        mg = MovesGen()
        x = CLion('Leo', OFFHEX) # CLion: skill=3
        army = Army(COLOR, [ x ])
        army2 = Army(COLOR2, [ ])
        # run
        moves = sorted( mg.moves(army, army2) ) # sorted() forces iterator to list
        # expected
        expected = sorted([
                # 1 step:
                [(x,0)], [(x,3)], [(x,7)], [(x,12)],
                # 2 steps:
                [(x,1)], [(x,4)], [(x,8)], [(x,13)], [(x,18)],
                # 3 steps:
                [(x,2)], [(x,5)], [(x,9)], [(x,14)], [(x,19)], [(x,23)],
                ])
        diff(self, moves, expected)

    def testEnterBattlefieldSkill4(self):
        """Test MovesGen for a creature with skill 4 entering"""
        mg = MovesGen()
        x = CCentaur('Carl', OFFHEX) # CCentaur: skill=4
        army = Army(COLOR, [ x ])
        army2 = Army(COLOR2, [ ])
        # run
        moves = sorted( mg.moves(army, army2) ) # sorted() forces iterator to list
        # expected
        expected = sorted([
                # 1 step:
                [(x,0)], [(x,3)], [(x,7)], [(x,12)],
                # 2 steps:
                [(x,1)], [(x,4)], [(x,8)], [(x,13)], [(x,18)],
                # 3 steps:
                [(x,2)], [(x,5)], [(x,9)], [(x,14)], [(x,19)], [(x,23)],
                # 4 steps:
                [(x,6)], [(x,10)], [(x,15)], [(x,20)], [(x,24)],
                ])
        diff(self, moves, expected)

    def testEnterBattlefieldTwoSkill1(self):
        """Test MovesGen for two creatures with skill 1 entering"""
        mg = MovesGen()
        x = CXlot1('Xaa', OFFHEX) # CXlot1: skill=1
        y = CYlot1('Ybb', OFFHEX) # CYlot1: skill=1 -- different Creature Race!
        army = Army(COLOR, [ x, y ])
        army2 = Army(COLOR2, [ ])
        # run
        moves = sorted( mg.moves(army, army2) ) # sorted() forces iterator to list
        # expected
        expected = sorted([
                [(x,0),(y,3)], [(x,0),(y,7)], [(x,0),(y,12)],
                [(x,3),(y,0)], [(x,3),(y,7)], [(x,3),(y,12)],
                [(x,7),(y,0)], [(x,7),(y,3)], [(x,7),(y,12)],
                [(x,12),(y,0)],[(x,12),(y,3)],[(x,12),(y,7)],
                ])
        diff(self, moves, expected)

    def testEliminateDoubles(self):
        """Test MovesGen eliminating identical situations"""
        mg = MovesGen()
        x = CXlot1('Xaa', OFFHEX) # CXlot1: skill=1
        y = CXlot1('Xbb', OFFHEX) # same Creature Race!!!
        army = Army(COLOR, [ x, y ])
        army2 = Army(COLOR2, [ ])
        # run
        moves = sorted( mg.moves(army, army2) )
        # expected
        expected = sorted([
                [(x,0),(y,3)], [(x,0),(y,7)], [(x,0),(y,12)],
                [(x,3),(y,7)], [(x,3),(y,12)],
                [(x,12),(y,7)],
                ])
        ## note that here might by a diff in ordering!!!
        diff(self, moves, expected)

    def testEnterBattlefieldTwoSkills(self):
        """Test MovesGen for two creatures with skills 1 and 2 entering"""
        mg = MovesGen()
        x = COgre('Otto', OFFHEX)
        y = CYlot1('Yll', OFFHEX)
        army = Army(COLOR, [ x, y ])
        army2 = Army(COLOR2, [ ])
        # run
        moves = sorted( mg.moves(army, army2) ) # sorted() forces iterator to list
        # expected
        expected = sorted([
                [(x,0),(y,3)], [(x,0),(y,7)], [(x,0),(y,12)],
                [(x,3),(y,0)], [(x,3),(y,7)], [(x,3),(y,12)],
                [(x,7),(y,0)], [(x,7),(y,3)], [(x,7),(y,12)],
                [(x,12),(y,0)],[(x,12),(y,3)],[(x,12),(y,7)],
                [(x,1), (y,0)],  [(x,1), (y,3)],  [(x,1), (y,7)],  [(x,1), (y,12)],
                [(x,4), (y,0)],  [(x,4), (y,3)],  [(x,4), (y,7)],  [(x,4), (y,12)],
                [(x,8), (y,0)],  [(x,8), (y,3)],  [(x,8), (y,7)],  [(x,8), (y,12)],
                [(x,13),(y,0)],  [(x,13),(y,3)],  [(x,13),(y,7)],  [(x,13),(y,12)],
                [(x,18),(y,0)],  [(x,18),(y,3)],  [(x,18),(y,7)],  [(x,18),(y,12)],
                ])
        diff(self, moves, expected)

    def testEnterBattlefieldBlocked(self):
        """Test MovesGen for not entering a blocked entrance"""
        mg = MovesGen()
        x = COgre('Otto', OFFHEX)
        a,b,c,d = CYlot1('Yla',0), CYlot1('Ylb',3), CYlot1('Ylc',7), CYlot1('Yld',12)
        army = Army(COLOR, [ x ])
        army2 = Army(COLOR2, [ a,b,c,d ])
        # run
        moves = sorted( mg.moves(army, army2) ) # sorted() forces iterator to list
        # expected
        expected = sorted([ [(x,27)] ])
        diff(self, moves, expected)

    def testEnterBattlefieldOutoftheway(self):
        """Test MovesGen with out-of-the-way opposing army entrance"""
        mg = MovesGen()
        x = CXlot1('Xlx', OFFHEX)
        for f in [1,4,8,13,18,6,10,15,20,24,11,16,21,25,17,22,26]:
            a = CYlot1('Yla', f)  ## out of the way, but present
            army = Army(COLOR, [ x ])
            army2 = Army(COLOR2, [ a ])
            # run
            moves = sorted( mg.moves(army, army2) ) # sorted() forces iterator to list
            # expected
            expected = sorted([ [(x,0)], [(x,3)], [(x,7)], [(x,12)] ])
            diff(self, moves, expected)

    def testEnterBattlefieldDisturbed(self):
        """Test MovesGen with disturbing opposing army entrance"""
        mg = MovesGen()
        x = CXlot1('Xlx', OFFHEX)
        for f in [0,3,7,12]:
            a = CYlot1('Yla', f)  ## out of the way, but present
            army = Army(COLOR, [ x ])
            army2 = Army(COLOR2, [ a ])
            # run
            moves = sorted( mg.moves(army, army2) ) # sorted() forces iterator to list
            # expected
            expected = sorted([ [(x,g)] for g in [0,3,7,12]  if g!=f ])
            diff(self, moves, expected)

    def testEnterBattlefieldNotEngaged(self):
        """Tests if an outfield creature is wrongly considered to be enganged."""
        # test makes sense if '3' could force the Engagement of OFFHEX
        self.assertTrue( getTerrain().couldStep(OFFHEX,  3) )
        mg = MovesGen()
        x = CXlot1('Xlx', OFFHEX)
        a = CYlot1('Yla', 3)  # at the side where OFFHEX enters
        army = Army(COLOR, [ x ])
        army2 = Army(COLOR2, [ a ])
        # run
        moves = sorted( mg.moves(army, army2) ) # sorted() forces iterator to list
        # expected
        expected = sorted([ [(x,0)], [(x,7)], [(x,12)] ])
        diff(self, moves, expected)

    def testEnterBattlefieldConstrained1(self):
        """Test MovesGen for entering an almost blocked entrance"""
        mg = MovesGen()
        x = COgre('Otto', OFFHEX)
        # leave '3' open:
        a,b,c,d = CYlot1('Yla',0), CYlot1('Ylb',4), CYlot1('Ylc',7), CYlot1('Yld',12)
        army = Army(COLOR, [ x ])
        army2 = Army(COLOR2, [ a,b,c,d ])
        # run
        moves = sorted( mg.moves(army, army2) ) # sorted() forces iterator to list
        # expected
        expected = sorted([ [(x,3)], [(x,8)] ])
        diff(self, moves, expected)

    def testEnterBattlefieldConstrained2(self):
        """Test MovesGen for entering an almost blocked entrance, again"""
        mg = MovesGen()
        x = COgre('Otto', OFFHEX)
        # leave '3' open:
        a,  c,d = CYlot1('Yla',0), CYlot1('Ylc',7), CYlot1('Yld',12)
        army = Army(COLOR, [ x ])
        army2 = Army(COLOR2, [ a,  c,d ])
        # run
        moves = sorted( mg.moves(army, army2) ) # sorted() forces iterator to list
        # expected
        expected = sorted([ [(x,3)], [(x,4)], [(x,8)] ])
        diff(self, moves, expected)

    def testEnterBattlefieldNonBlockedFlyer(self):
        """Test MovesGen for flying oder a blocked entrance"""
        mg = MovesGen()
        x = CGargoyle('Gerd', OFFHEX)
        a,b,c,d = CYlot1('Yla',0), CYlot1('Ylb',3), CYlot1('Ylc',7), CYlot1('Yld',12)
        army = Army(COLOR, [ x ])
        army2 = Army(COLOR2, [ a,b,c,d ])
        # run
        moves = sorted( mg.moves(army, army2) ) # sorted() forces iterator to list
        # expected
        expected = sorted([
                # 2 steps:
                [(x,1)], [(x,4)], [(x,8)], [(x,13)], [(x,18)],
                # 3 steps:
                [(x,2)], [(x,5)], [(x,9)], [(x,14)], [(x,19)], [(x,23)],
                ])
        diff(self, moves, expected)

    ### engaged tests

    def testEngaged(self):
        """Test that an engaged creature must not move."""
        self.assertTrue( getTerrain().couldStrike(3,  7) )
        mg = MovesGen()
        x = CXlot1('Xlx', 3)
        a = CYlot1('Yla', 7)  # near 3
        army = Army(COLOR, [ x ])
        army2 = Army(COLOR2, [ a ])
        # run
        moves = sorted( mg.moves(army, army2) )
        # expected
        expected = sorted([ [(x,3)] ])
        diff(self, moves, expected)

    def testEngagedNot(self):
        """Tests a creature can move with an enemy in sight."""
        self.assertFalse( getTerrain().couldStrike(3,  12) )
        mg = MovesGen()
        x = CXlot1('Xlx', 3)
        a = CYlot1('Yla', 12)  # NOT near 3
        army = Army(COLOR, [ x ])
        army2 = Army(COLOR2, [ a ])
        # run
        moves = sorted( mg.moves(army, army2) )
        # expected
        expected = sorted([ [(x,3)],  [(x,0)], [(x,4)],  [(x,7)],  [(x,8)] ])
        diff(self, moves, expected)

    def testEngagedAndNotEngaged(self):
        """Tests moves for an army with an enganged and an not-engaged creature."""
        self.assertFalse( getTerrain().couldStrike(3,  12) )
        self.assertTrue( getTerrain().couldStrike(7,  12) )
        mg = MovesGen()
        x = CXlot1('Xlx', 3) # not engaged
        y = CXlot1('Xlx', 7) # engaged
        a = CYlot1('Yla', 12)  # NOT near 3, but near 7
        army = Army(COLOR, [ x,  y ])
        army2 = Army(COLOR2, [ a ])
        # run
        # - the inner sorted() calls is not 100% correct here. do not copy blindly.
        moves = sorted( map(sorted,  mg.moves(army, army2) ))
        # expected
        # - the inner sorted() calls is not 100% correct here. do not copy blindly.
        expected = sorted( map(sorted, [ [(x,0), (y,7)], [(x,3), (y, 7)], [(x,4), (y,7)], [(x,8), (y,7)] ]) )
        diff(self, moves, expected)

    def testLongRoute(self):
        """Test MovesGen with long routes"""
        mg = MovesGen()
        army2 = Army(COLOR2, [ CXlot1('X%02d'%h, h)  for h in [0, 1, 5, 10, 20, 19, 7, 12, 8] ])
        y = CCentaur('Carl', 15) # engaged, but also in the way
        _run7 = [3, 4, 9, 14, 13, 18, 23]
        for _skill,  _runto in [
                                (7,_run7),
                                (8,_run7+[24]),
                                (9,_run7+[24,25]),
                                (10,_run7+[24,25,21,26]),
                                (11,_run7+[24,25,21,26, 16, 22]),
                               ]:
            x = CCentaur('Mv%s'%_skill, 27)
            x._skill = _skill
            army = Army(COLOR, [ x, y ])
            # run
            moves = sorted( mg.moves(army, army2) ) # sorted() forces iterator to list
            # expected
            expected = sorted( [ [(y,15),(x,h)]  for h in _runto ] )
            diff(self, moves, expected)
        return


class TestMarsh(unittest.TestCase):
    """Test Marshes with Bog hazards"""
    def setUp(selfself):
        setTerrain(tmarsh)
    def tearDown(self):
        setTerrain(tplains)

    def testBogForbidden(self):
        self.assertEquals( getTerrain().getHazardOfHex(8), Hazards.BOG )
        self.assertEquals( getTerrain().getHazardOfHex(7), Hazards.NONE )
        mg = MovesGen()
        x = CXlot1('Xlx', 7) # not native in Bog
        army = Army(COLOR, [ x ])
        army2 = Army(COLOR2, [ ])
        # run
        moves = sorted( mg.moves(army, army2) )
        # expected
        expected = sorted( [ [(x,h)]  for h in [7, 3,12,13] ] )
        diff(self, moves, expected)

    def testBogNative(self):
        t = getTerrain()
        self.assertEquals( t.getName(), "Marsh")
        self.assertEquals( t.getHazardOfHex(8), Hazards.BOG )
        self.assertEquals( t.getHazardOfHex(7), Hazards.NONE )
        mg = MovesGen()
        x = COgre('Otto', 7)
        self.assertTrue(x.isNativeHazard(t.getHazardOfHex(8)))
        army = Army(COLOR, [ x ])
        army2 = Army(COLOR2, [ ])
        # run
        moves = sorted( mg.moves(army, army2) )
        # expected
        expected = sorted( [ [(x,h)]  for h in [7, 0,3,12,4,8,13,18, 9,14,19] ] )
        diff(self, moves, expected)



###################################################

if __name__ == '__main__':
    unittest.main()
